﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Wrapper.Direct3D;
using Vortex.Wrapper;
using Vortex.Debugging;
using Vortex.Drawing.Performance;

namespace Vortex.Drawing {
	/// <summary>
	/// Simple drawing device event handler
	/// </summary>
	public delegate void DeviceEventHandler(DrawingDevice device);

	/// <summary>
	/// Base class for implementation of drawing device
	/// </summary>
	public abstract class DrawingDevice : IDisposable {
		/// <summary>
		/// Gets current drawing device (theoretically supports few device instances).
		/// </summary>
		/// <value>Currently active device.</value>
		public static DrawingDevice Current { get; private set; }

		protected Direct3DLibrary9 _library;
		protected Direct3DDevice9 _device;
		protected IntPtr _windowHandle;
		protected PrimaryDrawingContext _primaryDrawingContext;
		private Renderer2D _renderer;
		private List<Resource> _resources = new List<Resource>();
		private PerformanceMonitor _perfMonitor = new PerformanceMonitor();
		private DrawingDeviceInfo _info = new DrawingDeviceInfo();

		#region Properties


		#endregion

		protected DrawingDevice(IntPtr windowHandle, int bufferWidth, int bufferHeight, bool fullScreen, bool verticalSync) {
			using (Log.ImportantUp("Initializing DrawingDevice")) {
				Current = this;

				_windowHandle = windowHandle;
				_library = new Direct3DLibrary9();
				_primaryDrawingContext = new PrimaryDrawingContext(windowHandle, bufferWidth, bufferHeight, fullScreen, verticalSync);

				ObtainDeviceInformation();
				Initialize();
				

				_renderer = new Renderer2D(this);
				_renderer.Initialize();
				_primaryDrawingContext.OnCreateDevice();
			}
			
		}

		/// <summary>
		/// Initializes this instance.
		/// </summary>
		private void Initialize() {
			HResult result;
			
			PresentParameters presentParameters;
			_primaryDrawingContext.PreparePresentParameters(out presentParameters);

			//try to create device with pure HW vertex processing first
			result = _library.CreateDevice(0, D3DDeviceType.Hal, _windowHandle, D3DDeviceCreateFlags.PureDevice | D3DDeviceCreateFlags.HardwareVertexprocessing, ref presentParameters, out _device);

			if (result.IsFailed) {
				//try to create device with hardware vertex processing second
				result = _library.CreateDevice(0, D3DDeviceType.Hal, _windowHandle, D3DDeviceCreateFlags.HardwareVertexprocessing, ref presentParameters, out _device);

				if (result.IsFailed) {
					Log.Warning("Direct3DDevice doesn't support hardware vertex processing");
					
					//try to create device with software vertex processing
					result = _library.CreateDevice(0, D3DDeviceType.Hal, _windowHandle, D3DDeviceCreateFlags.SoftwareVertexprocessing, ref presentParameters, out _device);
					
					if (result.IsSuccess) {
						Log.Success("Direct3DDevice with software vertex processing was successfully created");
					} else {
						Watcher.Crush("Unable to create Direct3DDevice with HAL");
					}
				} else {
					Log.Success("Direct3DDevice with hardware vertex processing was successfully created");
				}
			} else {
				Log.Success("Pure Direct3DDevice was successfully created");
			}
		}


		/// <summary>
		/// Obtains the device information from Direct3D.
		/// </summary>
		private void ObtainDeviceInformation() {
			D3DCaps caps;
			D3DAdapterIdentifier identifier;

			HResult result = _library.GetDeviceCaps(0, out caps);
			Watcher.Assert(result, "Getting device caps");

			result = _library.GetAdapterIdentifier(0, out identifier);
			Watcher.Assert(result, "Getting adapter identifier");

			_info.AdapterName = identifier.AdapterName;
			_info.DriverName = identifier.DriverName;
			_info.PureDeviceSupported = (caps.DevCaps & D3DDeviceCaps.PUREDEVICE) != D3DDeviceCaps.NONE;

			_info.MaxTextureWidth = (int)caps.MaxTextureWidth;
			_info.MaxTextureHeight = (int)caps.MaxTextureHeight;
			_info.MaxTextureRepeat = (int)caps.MaxTextureRepeat;
			_info.MaxTextureAspectRatio = (int)caps.MaxTextureAspectRatio;
			_info.MaxDrawPrimitiveCount = (int)caps.MaxDrawPrimitive;

			//determine texture size options
			if ((caps.TextureCaps & D3DTextureCaps.POW2) != D3DTextureCaps.NONE ) {
				if ((caps.TextureCaps & D3DTextureCaps.NONPOW2CONDITIONAL) != D3DTextureCaps.NONE) {
					_info.TextureSizeOptions = TextureSizeOptions.NonPow2Conditional;
				} else {
					_info.TextureSizeOptions = TextureSizeOptions.Pow2Only;
				}
			} else {
				_info.TextureSizeOptions = TextureSizeOptions.NonPow2;
			}

			//do we support square textures only
			_info.SquareTexturesOnly = (caps.TextureCaps & D3DTextureCaps.SQUAREONLY) != D3DTextureCaps.NONE;

			//log startup information
			using (Log.ImportantUp("Device Information")) {
				Log.Trace("Adapter: {0}", _info.AdapterName);
				Log.Trace("Driver: {0}", _info.DriverName);
				Log.Trace("Supports Pure Device: {0}", _info.PureDeviceSupported ? "Yes" : "No");
				Log.Trace("Max Texture Size: {0}x{1}", _info.MaxTextureWidth, _info.MaxTextureHeight);
				Log.Trace("Max Texture Repeat: {0}", _info.MaxTextureRepeat);
				Log.Trace("Max Texture Aspect Ratio: {0}", _info.MaxTextureAspectRatio);
				Log.Trace("Texture Size Options: {0}", _info.TextureSizeOptions);
				Log.Trace("Square Textures Only: {0}", _info.SquareTexturesOnly ? "Yes" : "No");
			}
		}


		private void DoReset() {
			if (BeforeReset != null) BeforeReset(this);			

			PresentParameters presentParameters;
			_primaryDrawingContext.PreparePresentParameters(out presentParameters);

			HResult result = _device.Reset(ref presentParameters);
			Watcher.AssertCritical(result, "Reset device");
			Log.Trace("Direct3DDevice9 was reset successfully.");

			if (AfterReset != null) AfterReset(this);
		}


		/// <summary>
		/// Tries the reset third device.
		/// </summary>
		/// <returns>true if device was successfully reset</returns>
		public bool TryReset() {
			HResult result = _device.TestCooperativeLevel();
			if (result.IsSuccess || result == HResult.D3DError_DeviceNotReset) {
				DoReset();
				return true;
			} else {
				return false;
			}
		}

		/// <summary>
		/// Flushes the renderer command stack.
		/// </summary>
		public void Flush() {
			_renderer.Flush();
		}

		/// <summary>
		/// Begins the scene (initialize device for scene rendering).
		/// </summary>
		/// <param name="context">The drawing context.</param>
		/// <returns><c>true</c> if scene was initialized successfully and rendering is allowed; otherwise <c>false</c></returns>
		public bool BeginScene() {
			
			if (_device.TestCooperativeLevel().IsSuccess && _device.BeginScene().IsSuccess) {
				_perfMonitor.Reset();
#if PERF_STATS
				_perfMonitor.StartTimer(DeviceTimer.Frame);
#endif
				_renderer.BeginScene();
				return true;
			} else {
				return false;
			}
		}

		/// <summary>
		/// Ends the scene (finalize device internal stuff to ensure everything is rendered).
		/// </summary>
		/// <param name="context">The drawing context.</param>
		public void EndScene() {
			_renderer.EndScene();
#if PERF_STATS
			_perfMonitor.StopTimer(DeviceTimer.Frame);
#endif
			_device.EndScene();
		}

		/// <summary>
		/// Gets the available texture memory in megabytes.
		/// </summary>
		/// <value>The available texture memory in megabytes.</value>
		public int AvailableTextureMemory {
			get {
				return (int)(_device.GetAvailableTextureMem() / 1024 / 1024);
			}
		}

		/// <summary>
		/// Gets the performance monitor instance for this device.
		/// </summary>
		/// <value>Device performance monitor instance.</value>
		public PerformanceMonitor PerfMonitor {
			get { return _perfMonitor; }
		}

		/// <summary>
		/// Gets the info about this drawing device.
		/// </summary>
		/// <value>The drawing device info.</value>
		public DrawingDeviceInfo Info {
			get { return _info; }
		}

		#region Device Regions

		/// <summary>
		/// Occurs before device is going to be reset.
		/// </summary>
		public event DeviceEventHandler BeforeReset;

		/// <summary>
		/// Occurs after device was reset successfully.
		/// </summary>
		public event DeviceEventHandler AfterReset;

		#endregion

		#region Internal Section

		/// <summary>
		/// Registers the resource in device internal list.
		/// </summary>
		/// <param name="resource">The resource instance.</param>
		internal void RegisterResource(Resource resource) {
			_resources.Add(resource);
		}

		/// <summary>
		/// Unregisters the resource from device internal list.
		/// </summary>
		/// <param name="resource">The resource instance.</param>
		internal void UnregisterResource(Resource resource) {
			_resources.Remove(resource);
		}

		/// <summary>
		/// Gets the handle of DrawingDevice - reference to Direct3DDevice9 object.
		/// </summary>
		/// <value>Direct3DDevice9 reference.</value>
		protected internal Direct3DDevice9 ApiRef {
			get { return _device; }
		}

		/// <summary>
		/// Gets the 2D renderer for this device.
		/// </summary>
		/// <value>The 2D renderer instance.</value>
		internal Renderer2D Renderer {
			get { return _renderer; }
		}

		#endregion


		#region IDisposable Members

		public void Dispose() {
            
            //fixed by feedback
            Current = null;

            _resources.Clear();
            _primaryDrawingContext.Dispose();
            _renderer.Destroy();
            //release native wrappers, tricky but seems there are an issue with ref counting of _device object
            _device.Dispose();
            _library.Dispose();             
		}

		#endregion
	}
}
